package ar.com.spsolutions.splibrary.utils;

import java.io.File;
import java.net.URLClassLoader;
import java.util.List;

import ar.com.spsolutions.splibrary.entity.Entity;
import ar.com.spsolutions.splibrary.entity.IEntity;

/**
 * Busca todas las subclases de Entity o IEntity si es una interface
 * 
 * @author leezsi
 * 
 */
public class EntitySubclassFinder {
	// realizo la busqueda
	public void find(final URLClassLoader classLoader, final File file,
			final List<Class<?>> collector, final String packageName) {
		// Si es un directorio me fijo en sus hijos
		if (file.isDirectory()) {
			for (final File child : file.listFiles()) {
				// Si es una clase java chequeo si es una entidad
				if (this.isJavaClass(child)) {
					if (this.isEntitySubclass(classLoader, packageName, child)) {
						collector.add(this.getClassFromFile(classLoader,
								packageName, child));
					}
					// Sino es un mando a buscar en sus subdirectorios
				} else {
					new EntitySubclassFinder().find(classLoader, child,
							collector, packageName + "." + child.getName());
				}
			}
		}
	}

	private boolean isEntitySubclass(final URLClassLoader classLoader,
			final String packageName, final File child) {
		// Cargo la clase
		final Class<?> target = this.getClassFromFile(classLoader, packageName,
				child);
		// Si es una interface
		if ((target != null) && target.isInterface()) {
			// No esta mapeada la interface IEntity, por eso la descarto
			if (target.equals(IEntity.class)) {
				return false;
			}
			try {
				// Intento castear, ya que getSubclass() en las interfaces no
				// funcionan
				target.asSubclass(IEntity.class);
				// Si es subclase de IEntity entonces la agrego
				return true;
			} catch (final ClassCastException e) {
				// Si salta la excepcion es que no es una entidad

				return false;
			}
		}
		// Si no es una interface me fijo si la es subclase de Entity
		return (target != null) && this.isSubclass(target);

	}

	private boolean isSubclass(final Class<?> target) {
		// Me fijo si es subclase directa o indirecta de Entity
		final Class<?> superclass = target.getSuperclass();
		if (superclass == null) {
			return false;
		}
		if (!superclass.equals(Entity.class)) {
			return this.isSubclass(superclass);
		}
		return true;
	}

	private Class<?> getClassFromFile(final URLClassLoader classLoader,
			final String packageName, final File child) {
		// Cargo la clase
		try {
			return classLoader.loadClass(packageName.replaceFirst("\\.", "")
					+ "." + child.getName().replaceAll("\\.class", ""));
		} catch (final ClassNotFoundException e) {
			return null;
		}
	}

	// Me fijo si es una clase
	private boolean isJavaClass(final File child) {
		return child.isFile() && child.getName().endsWith(".class");
	}
}
